#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
#include "ruby-m3g-internal.hpp"
using namespace m3g;
using namespace std;


namespace {
    struct ChildAccessor {
        Group* group;
    };
    VALUE rb_cGroup_ChildAccessor;
}

void ruby_Group_mark (Group* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_Group_free (Group* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~Group ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_Group_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(Group));
    return Data_Wrap_Struct (self, ruby_Group_mark, ruby_Group_free, p);
}

VALUE ruby_Group_initialize (VALUE self)
{
    Group* p;
    Data_Get_Struct (self, Group, p);
    __TRY__;
    new (p) Group;
    __CATCH__;
    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_Group_get_child (VALUE self)
{
    Group* p;
    Data_Get_Struct (self, Group, p);
    ChildAccessor* accessor;
    VALUE      val_accessor = Data_Make_Struct (rb_cGroup_ChildAccessor, ChildAccessor, 0, -1, accessor);
    accessor->group = p;
    return val_accessor;
}

VALUE ruby_Group_get_child_count (VALUE self)
{
    Group* p;
    Data_Get_Struct (self, Group, p);
    int count;
    __TRY__;
    count = p->getChildCount();
    __CATCH__;
    return INT2NUM(count);
}


VALUE ruby_Group_pick (int argc, VALUE* argv, VALUE self)
{
    Group* p;
    Data_Get_Struct (self, Group, p);

    VALUE val_arg1, val_arg2, val_arg3, val_arg4, val_arg5, val_arg6, val_arg7;
    int num = rb_scan_args (argc, argv, "43", &val_arg1, &val_arg2, &val_arg3, &val_arg4, &val_arg5, &val_arg6, &val_arg7);

    RayIntersection* ri;
    VALUE        val_ri = Data_Make_Struct (rb_cRayIntersection, RayIntersection, ruby_RayIntersection_mark, ruby_RayIntersection_free, ri);
    ruby_RayIntersection_initialize (val_ri);
    bool picked;

    switch (num) {
    case 4: {
        VALUE val_scope  = val_arg1;
        VALUE val_x      = val_arg2;
        VALUE val_y      = val_arg3;
        VALUE val_cam    = val_arg4;
        if (!rb_obj_is_kind_of (val_cam, rb_cCamera)) {
            rb_raise (rb_eArgError, "wrong argument type (expected M3G::Camera)");
        }
        int   scope = NUM2INT(val_scope);
        float x     = NUM2DBL(val_x);
        float y     = NUM2DBL(val_y);
        Camera* cam;
        Data_Get_Struct (val_cam, Camera, cam);
        __TRY__;
        picked = p->pick (scope, x, y, cam, ri);
        __CATCH__;
        break;
    }
    case 7: {
        VALUE val_scope = val_arg1;
        VALUE val_ox    = val_arg2;
        VALUE val_oy    = val_arg3;
        VALUE val_oz    = val_arg4;
        VALUE val_dx    = val_arg5;
        VALUE val_dy    = val_arg6;
        VALUE val_dz    = val_arg7;
        int   scope = NUM2INT(val_scope);
        float ox    = NUM2DBL(val_ox);
        float oy    = NUM2DBL(val_oy);
        float oz    = NUM2DBL(val_oz);
        float dx    = NUM2DBL(val_dx);
        float dy    = NUM2DBL(val_dy);
        float dz    = NUM2DBL(val_dz);
        RayIntersection* ri;
        Data_Get_Struct (val_ri, RayIntersection, ri);
        __TRY__;
        picked= p->pick (scope, ox, oy, oz, dx, dy, dz, ri);
        __CATCH__;
        break;
    }
    default: {
        rb_raise (rb_eArgError, "wrong number of arguments (%d for 4 or 7)", num);
    }
    }

    VALUE val_hit = picked ? Qtrue : Qfalse;
    VALUE val_ret = rb_ary_new2 (2);
    rb_ary_push (val_ret, val_hit);
    rb_ary_push (val_ret, val_ri );
    return val_ret;
}

VALUE ruby_Group_remove_child (VALUE self, VALUE val_child)
{
    if (!rb_obj_is_kind_of (val_child, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)");
    }
    Group* p;
    Data_Get_Struct (self, Group, p);
    Node* child;
    Data_Get_Struct (val_child, Node, child);
    __TRY__;
    p->removeChild (child);
    __CATCH__;
    return Qnil;
}

VALUE ruby_Group_print (VALUE self)
{
    Group* p;
    Data_Get_Struct (self, Group, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 * Group_ChildAccessor
 */

VALUE ruby_Group_ChildAccessor_get_child (VALUE self, VALUE val_index)
{
    ChildAccessor* p;
    Data_Get_Struct (self, ChildAccessor, p);
    int   index = NUM2INT (val_index);
    Node* child;
    __TRY__;
    child = p->group->getChild (index);
    __CATCH__;
    return child ? (VALUE)child->getExportedEntity() : Qnil;
}

VALUE ruby_Group_ChildAccessor_add_child (VALUE self, VALUE val_child)
{
    if (!rb_obj_is_kind_of (val_child, rb_cNode)) {
        rb_raise (rb_eArgError, "wrong argument type (expected M3G::Node)");
    }
    ChildAccessor* p;
    Data_Get_Struct (self, ChildAccessor, p);
    Node* child;
    Data_Get_Struct (val_child, Node, child);
    __TRY__;
    p->group->addChild (child);
    __CATCH__;
    return Qnil;
}

/**
 *
 */
void register_Group ()
{
    // Group
    rb_cGroup               = rb_define_class_under (rb_mM3G, "Group",               rb_cNode);

    rb_define_alloc_func (rb_cGroup, ruby_Group_allocate);
    rb_define_private_method (rb_cGroup, "initialize", (VALUE(*)(...))ruby_Group_initialize, 0);

    rb_define_method (rb_cGroup, "child",          (VALUE(*)(...))ruby_Group_get_child,        0);
    rb_define_method (rb_cGroup, "child_count",    (VALUE(*)(...))ruby_Group_get_child_count,  0);
    rb_define_method (rb_cGroup, "pick",           (VALUE(*)(...))ruby_Group_pick,            -1);
    rb_define_method (rb_cGroup, "remove_child",   (VALUE(*)(...))ruby_Group_remove_child,     1);
    rb_define_method (rb_cGroup, "to_s",           (VALUE(*)(...))ruby_Group_print,            0);

    // Group_ChildAccessor
    rb_cGroup_ChildAccessor  = rb_define_class_under (rb_cGroup, "ChildAccessor", rb_cObject);

    rb_define_method (rb_cGroup_ChildAccessor, "[]",        (VALUE(*)(...))ruby_Group_ChildAccessor_get_child,   1);
    rb_define_method (rb_cGroup_ChildAccessor, "<<",        (VALUE(*)(...))ruby_Group_ChildAccessor_add_child,   1);
}
